﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Json;
using System.IO;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Globalization;

namespace OBNEPGGrabber
{
    static class EPGFetcher
    {
        private static DirectoryInfo m_dirInfo = null;
        private static string m_strEPGURL;
        private static IStatusUpdate m_IUpdate = null;
        private static List<string> m_lstNames = null;
        private static TextWriter m_tw = null;
        private static List<string> m_lstGenres = null;

        // Here, all EPG data is fetched AND stored to file
        // For Onsbrabantnet, the resulting XML file is about 20MB
        public static void FetchEPG(string strEPGURL, DirectoryInfo dirInfo, int iDaysToFetch, ref List<Channel> lstChannel, IStatusUpdate IUpdate)
        {
            m_IUpdate = IUpdate;
            m_dirInfo = dirInfo;
            m_strEPGURL = strEPGURL;
            m_lstNames = new List<string>();
            m_lstGenres = new List<string>();
            // Create the target XMLTV file
            m_tw = new StreamWriter(dirInfo.FullName + "\\OBN.xml", false, Encoding.UTF8);

            // Add channels to XMLTV target file
            GenerateChannelXML(lstChannel);

            // Fetch the specified amount of days...or at least try to...data may not be present
            // The amount of parts of day is fixed to 9 (empirical figure)
            for (int iDay = 0; iDay < iDaysToFetch; iDay++)
            {
                for (int iPartOfDay = 1; iPartOfDay < 9; iPartOfDay++)
                {
                    // Fetch part of a day...
                    FetchPartOfDay(iDay, iPartOfDay);
                }
            }

            // Close the target XMLTV file
            CloseXML();
            m_tw.Close();

            // And copy it to the ProgramData folder of DVBLink
            string strDest = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + "\\DVBLogic\\DVBLink\\xmltv\\OBN.xml";
            File.Copy(dirInfo.FullName + "\\OBN.xml", strDest, true);
        }

        private static void FetchPartOfDay(int iDay, int iPartOfDay)
        {
            // iDay is zero based
            DateTime dtDateToFetch = DateTime.Today.Add(new TimeSpan(iDay * 24, 0, 0));
            string strDayToFetch = String.Format("{0:yyyyMMdd}.{1}.json", dtDateToFetch, iPartOfDay.ToString());
            // Only fetch a file in case a file is not present
            if (!File.Exists(m_dirInfo.FullName + "\\epgdata." + strDayToFetch))
            {
                FileFetcher.Fetch(m_strEPGURL + "epgdata." + strDayToFetch + ".gz", m_dirInfo, "epgdata." + strDayToFetch + ".gz", m_IUpdate);
                Unzipper.Unzip("epgdata." + strDayToFetch + ".gz", m_dirInfo, "epgdata." + strDayToFetch, true, m_IUpdate);
            }
            // Fetch the data from file
            FetchEPGData(strDayToFetch);
        }

        private static void FetchEPGData(string strDayToFetch)
        {
            string strFileToParse = m_dirInfo.FullName + "\\epgdata." + strDayToFetch;
            JsonTextParser jsonParser = new JsonTextParser();

            if (File.Exists(strFileToParse))
            {
                string text = System.IO.File.ReadAllText(strFileToParse);

                JsonObject jsonObjects = jsonParser.Parse(text);

                foreach (JsonObject jsonObj in jsonObjects as JsonObjectCollection)
                {
                    for (int i = 0; i < ((JsonArrayCollection)jsonObj).Count; i++)
                    {
                        var o = ((JsonArrayCollection)jsonObj)[i];
                        foreach (JsonObject obj in o as JsonObjectCollection)
                        {
                            // There are multiple programs in this json file
                            // All files must be downloaded first and then parsed
                            if (obj.Name == "id")
                            {
                                string strID = (string)obj.GetValue();
                                string strProgramURL = strID.Substring(strID.Length - 2, 2) + "\\" + strID + ".json";
                                // Only fetch a file in case a file is not present
                                if (!File.Exists(m_dirInfo.FullName + "\\" + strID + ".json"))
                                {
                                    FileFetcher.Fetch(m_strEPGURL + strProgramURL, m_dirInfo, strID + ".json", m_IUpdate);
                                }
                                // Convert a program to XMLTV format
                                ConvertToXMLTV(jsonObj.Name, m_dirInfo.FullName + "\\" + strID + ".json");
                            }
                        }
                    }
                }
            }
        }

        // Previously, all channels were obtained from the OBN server
        // this data is first saved to the XMLTV target file
        private static void GenerateChannelXML(List<Channel> lstChannel)
        {
            m_tw.WriteLine("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
            m_tw.WriteLine("<!DOCTYPE tv SYSTEM \"xmltv.dtd\">");
            m_tw.WriteLine();
            m_tw.WriteLine("<tv generator-info-name=\"OBNSuite\">");
            foreach (Channel ch in lstChannel)
            {
                m_tw.WriteLine(String.Format("<channel id=\"{0}\">", ch.Name));
                //                m_tw.WriteLine(String.Format("<channel id=\"{0}\">", ch.ChannelNo.ToString()));
                m_tw.WriteLine(String.Format("  <display-name lang=\"nl\">{0}</display-name>", ch.Name));
                m_tw.WriteLine(String.Format("  <display-name lang=\"nl\">{0}</display-name>", FixXMLString(ch.FriendlyName)));
                m_tw.WriteLine("</channel>");
                m_tw.WriteLine();
            }
        }

        // Given program data, generate the XML
        private static void GenerateProgramXML(XMLTVProgram program)
        {
            m_tw.WriteLine(String.Format("<programme start=\"{0}\" stop=\"{1}\" channel=\"{2}\">", program.Start, program.End, program.Channel));
            m_tw.WriteLine(String.Format("  <title lang=\"nl\">{0}</title>", program.Name));
            if (program.EpisodeTitle != null)
            {
                m_tw.WriteLine(String.Format("  <sub-title lang=\"nl\">{0}</sub-title>", program.EpisodeTitle));
            }
            m_tw.WriteLine(String.Format("  <desc lang=\"nl\">{0}</desc>", program.Description));
            if (program.GetDirectors() != null || program.GetActors() != null)
            {
                m_tw.WriteLine(String.Format("  <credits>"));
                if (program.GetDirectors() != null)
                {
                    foreach (string director in program.GetDirectors())
                        m_tw.WriteLine(String.Format("    <director>{0}</director>", director));
                }
                if (program.GetActors() != null)
                {
                    foreach (string actor in program.GetActors())
                        m_tw.WriteLine(String.Format("    <actor>{0}</actor>", actor));
                }
                m_tw.WriteLine(String.Format("  </credits>"));
            }
            if (program.GetGenres() != null)
            {
                foreach (string genre in program.GetGenres())
                    m_tw.WriteLine("  <category>{0}</category>", genre);
            }
            if (program.AspectRatio != null)
            {
                m_tw.WriteLine("  <video>");
                m_tw.WriteLine(String.Format("    <aspect>{0}</aspect>", program.AspectRatio));
                m_tw.WriteLine("  </video>");
            }
            m_tw.WriteLine("</programme>");
        }

        private static void CloseXML()
        {
            m_tw.WriteLine("</tv>");
        }

        // There may be some characters that need fixing before they can be
        // put in an XML file.
        private static string FixXMLString(string strStringToFix)
        {
            string strFixedString = strStringToFix;
            Regex rx = new Regex(@"\\[uU]([0-9A-Fa-f]{4})");

            strFixedString = rx.Replace(strFixedString, match => ((char)Int32.Parse(match.Value.Substring(2), NumberStyles.HexNumber)).ToString());
            strFixedString = strFixedString.Replace("<", "&lt;");
            strFixedString = strFixedString.Replace(">", "&gt;");
            strFixedString = strFixedString.Replace("&", "&amp;");
            strFixedString = strFixedString.Replace("\"", "&quot;");
            strFixedString = strFixedString.Replace("'", "&apos;");

            return strFixedString;
        }

        // Parse program data
        private static void ConvertToXMLTV(string strChannel, string strJsonFile)
        {
            JsonTextParser jsonParser = new JsonTextParser();

            try
            {
                string text = System.IO.File.ReadAllText(strJsonFile);
                JsonObject jsonObjects = jsonParser.Parse(text);
                XMLTVProgram program = new XMLTVProgram();
                program.Channel = strChannel;

                foreach (JsonObject jsonObj in jsonObjects as JsonObjectCollection)
                {
                    switch (jsonObj.Name)
                    {
                        case "name":
                            program.Name = (string)jsonObj.GetValue();
                            program.Name = FixXMLString(program.Name);
                            break;
                        case "start":
                            program.Start = (string)jsonObj.GetValue().ToString();
                            DateTime start = new DateTime(1970, 1, 1).AddSeconds(double.Parse(program.Start));
                            program.Start = String.Format("{0:yyyyMMddHHmmss}", start);
                            break;
                        case "end":
                            program.End = (string)jsonObj.GetValue().ToString();
                            DateTime end = new DateTime(1970, 1, 1).AddSeconds(double.Parse(program.End));
                            program.End = String.Format("{0:yyyyMMddHHmmss}", end);
                            break;
                        case "description":
                            program.Description = (string)jsonObj.GetValue();
                            program.Description = FixXMLString(program.Description);
                            break;
                        case "episodeTitle":
                            program.EpisodeTitle = (string)jsonObj.GetValue();
                            program.EpisodeTitle = FixXMLString(program.EpisodeTitle);
                            break;
                        case "genres":
                            for (int i = 0; i < ((JsonArrayCollection)jsonObj).Count; i++)
                            {
                                JsonObject o = ((JsonArrayCollection)jsonObj)[i];
                                string strGenre = (string)o.GetValue();
                                strGenre = FixXMLString(strGenre);
                                // Map genres to genres known by Windows Media Center
                                // This allows for color coding of programs in the MCE EPG
                                switch (strGenre)
                                {
                                    case "Actualiteit":
                                    case "Info":
                                        strGenre = "News";
                                        break;
                                    case "Jeugd":
                                        strGenre = "Family";
                                        break;
                                    case "Film":
                                        strGenre = "Movies";
                                        break;
                                    default:
                                        break;
                                }
                                program.AddGenre(strGenre);
                            }
                            break;
                        case "nicamParentalRating":
                            program.NicamParentalRating = (string)jsonObj.GetValue();
                            break;
                        case "nicamWarning":
                            program.NicamWarning = (string)jsonObj.GetValue().ToString();
                            break;
                        case "actors":
                            for (int i = 0; i < ((JsonArrayCollection)jsonObj).Count; i++)
                            {
                                JsonObject o = ((JsonArrayCollection)jsonObj)[i];
                                string strActor = (string)o.GetValue();
                                strActor = FixXMLString(strActor);
                                program.AddActor(strActor);
                            }
                            break;
                        case "aspectratio":
                            program.AspectRatio = (string)jsonObj.GetValue();
                            break;
                        case "directors":
                            for (int i = 0; i < ((JsonArrayCollection)jsonObj).Count; i++)
                            {
                                JsonObject o = ((JsonArrayCollection)jsonObj)[i];
                                string strDirector = (string)o.GetValue();
                                strDirector = FixXMLString(strDirector);
                                program.AddDirector(strDirector);
                            }
                            break;
                        case "presenters":
                            for (int i = 0; i < ((JsonArrayCollection)jsonObj).Count; i++)
                            {
                                JsonObject o = ((JsonArrayCollection)jsonObj)[i];
                                string strPresenter = (string)o.GetValue();
                                strPresenter = FixXMLString(strPresenter);
                                program.AddPresenter(strPresenter);
                            }
                            break;
                        default:
                            break;
                    }
                }
                GenerateProgramXML(program);
            }
            catch (Exception)
            {

            }
        }
    }
}
